import { goTo } from '@/modules/move'
import { UpgradeMemory } from './types'
import { BodyPartArrayType, getCreepWorking } from '@/utils/common'
import { CONTROLLER_ENERGY } from '@/utils/constants'
import { getFreeSpace } from '@/utils/position'
import { colors, getRange, log } from '@/ayaka'
import { creepPosBipartiteMatch } from '@/algorithm/bipartiteMatch'

// upgrade 能量来源建筑的最低能量，低于这个值就不会被 upgrader 使用
const MIN_ENERGY = 800

/**
 * upgrader 工作函数
 */
export const upgraderWork = function (room: MyRoom, upgraders: Creep[], upgradeMemory: UpgradeMemory) {
    const creeps = upgraders.filter((creep) => !creep.spawning)
    if (!creeps.length) return

    creeps.forEach((creep) => {
        if (creep.memory.boost && !creep.memory.boost.done) {
            return
        }

        getCreepWorking(creep)
        delete creep.memory.stand
        if (creep.memory.slack) return

        if (creep.memory.working) {
            if (Game.time % 71 === 0) {
                const struct = Game.getObjectById(creep.memory.sourceId as Id<StructureContainer>)
                if (struct && struct.hits < struct.hitsMax * 0.9) {
                    creep.repair(struct)
                }
                delete creep.memory.sourceId
            }

            if (creep.upgradeController(room.controller) === ERR_NOT_IN_RANGE) {
                goTo(creep, room.controller.pos)
                return
            }

            if (creep.pos.getRangeTo(room.controller) === 3) {
                creep.memory.stand = true
            }

            if (!creep.memory.bodyParts) {
                creep.memory.bodyParts = {
                    [WORK]: creep.body.filter((b) => b.type === WORK).length,
                }
            }

            // 能量不足
            if (creep.store['energy'] <= (creep.memory.bodyParts![WORK] || 5)) {
                let struct: AnyStoreStructure | null = null

                if (creep.memory.sourceId) {
                    struct = Game.getObjectById(creep.memory.sourceId as Id<AnyStoreStructure>)
                }

                if (!struct || !creep.pos.isNearTo(struct) || struct.store['energy'] < MIN_ENERGY) {
                    delete creep.memory.sourceId
                    struct = getNearbyEnergyStructure(room, creep, upgradeMemory)
                }

                if (!struct) return

                creep.memory.sourceId = struct.id
                if (creep.pos.getRangeTo(struct) <= 1) {
                    creep.withdraw(struct, 'energy')
                }
            }
        } else {
            if (!creep.memory.sourceId) {
                let struct: AnyStoreStructure | null = null
                if (upgradeMemory.linkId) {
                    struct = Game.getObjectById(upgradeMemory.linkId)
                    if (struct) {
                        creep.memory.sourceId = struct.id
                    } else {
                        delete upgradeMemory.linkId
                        struct = getNearbyEnergyStructure(room, creep, upgradeMemory)
                    }
                } else {
                    struct = getNearbyEnergyStructure(room, creep, upgradeMemory)
                }

                if (struct) {
                    creep.memory.sourceId = struct.id
                }
            }

            if (!creep.memory.sourceId) {
                creep.memory.slack = 10
                return
            }

            const struct = Game.getObjectById(creep.memory.sourceId as Id<AnyStoreStructure>)
            if (!struct || (struct instanceof StructureContainer && struct.store['energy'] < MIN_ENERGY)) {
                delete creep.memory.sourceId
                return
            }

            if (creep.withdraw(struct, 'energy') === ERR_NOT_IN_RANGE) {
                goTo(creep, struct.pos)
            }
        }
    })

    let upgradeNextPosFlag: Flag | null = null
    if (
        creeps.length >= 2 &&
        room.controller.level < 8 &&
        (Game.time % 31 === 0 ||
            ((upgradeNextPosFlag = Game.flags[`upgrade_${room.name}_nextPos`]) &&
                upgradeNextPosFlag.color === COLOR_WHITE))
    ) {
        const a = Game.cpu.getUsed()
        const upgraderContainers = upgradeMemory.containerIds
            .map(Game.getObjectById)
            .filter(Boolean) as StructureContainer[]

        // 如果 storage 或 terminal 在控制器附近，就不用 container 了
        if (
            (room.storage && room.storage.pos.getRangeTo(room.controller) <= 2) ||
            (room.terminal && room.terminal.pos.getRangeTo(room.controller) <= 2)
        ) {
            upgraderContainers.length = 0
        }

        const storages = (
            [...upgraderContainers, room.storage, room.terminal].filter(Boolean) as AnyStoreStructure[]
        ).filter((s) => getRange(s.pos.x, s.pos.y, room.controller.pos.x, room.controller.pos.y) <= 3)

        if (!storages.length) {
            return
        }

        const posMap = new Map<string, RoomPosition>()
        const range = 1
        storages.forEach((obj) => {
            const freePos = getFreeSpace(obj.pos, { range })
            if (obj instanceof StructureContainer) {
                freePos.push(obj.pos)
            }
            for (const pos of freePos) {
                if ((pos._dis = getRange(pos.x, pos.y, room.controller.pos.x, room.controller.pos.y)) > 3) continue
                // 不要占房间中心点的位置
                const center = room.memory.center
                if (center && pos.x === center[0] && pos.y === center[1]) continue

                if (!posMap.has(`${pos.x}/${pos.y}`)) {
                    posMap.set(`${pos.x}/${pos.y}`, pos)
                }
            }
        })

        const posArr = [...posMap.values()]
        posArr.sort((a, b) => a._dis - b._dis)
        creeps.forEach((c) => (c.pos._dis = getRange(c.pos.x, c.pos.y, room.controller.pos.x, room.controller.pos.y)))
        creeps.sort((a, b) => a.pos._dis - b.pos._dis)

        const result = creepPosBipartiteMatch(creeps, posArr, 1)

        if (upgradeNextPosFlag) {
            // 可视化
            creeps.forEach((creep) => {
                const targetPos = result[creep.name]
                if (targetPos && !creep.pos.isEqualTo(targetPos)) {
                    room.visual.line(creep.pos, targetPos, { color: colors.red })
                }
            })
        } else {
            creeps.forEach((creep) => {
                const targetPos = result[creep.name]
                if (targetPos && !creep.pos.isEqualTo(targetPos)) {
                    creep.move(creep.pos.getDirectionTo(targetPos))
                }
            })
        }

        log(`更新位置消耗cpu: ${Game.cpu.getUsed() - a}`, ['upgrade'], 'purple')
    }
}

/**
 * 获取离控制器最近的有足够能量的建筑
 */
function getNearbyEnergyStructure(room: MyRoom, creep: Creep, upgradeMemory: UpgradeMemory) {
    // return room.controller.pos.findClosestByRange(getEnergyStructure(room, upgradeMemory))
    return creep.pos.findClosestByRange(getEnergyStructure(room, upgradeMemory))
}

/**
 * 获取有能量的建筑
 */
function getEnergyStructure(room: MyRoom, upgradeMemory: UpgradeMemory) {
    const structures: AnyStoreStructure[] = []

    // 先看 storage 和 terminal
    for (const struct of [room.storage, room.terminal]) {
        if (struct && struct.store['energy'] > 20000) structures.push(struct)
    }

    // 再看 link
    if (upgradeMemory.linkId) {
        const link = Game.getObjectById(upgradeMemory.linkId)
        if (link) {
            if (link.store['energy'] > 0) structures.push(link)
        } else {
            delete upgradeMemory.linkId
        }
    }

    // 再看 container
    if (upgradeMemory.containerIds?.length) {
        for (const id of upgradeMemory.containerIds) {
            const container = Game.getObjectById(id)
            if (container) {
                if (container.store['energy'] > MIN_ENERGY) structures.push(container)
            } else {
                upgradeMemory.containerIds = upgradeMemory.containerIds.filter((id) => id !== id)
            }
        }
    }

    return structures
}

/**
 * 生成 upgrader 的体型
 */
export const createUpgraderBody = (room: Room): BodyPartArrayType => {
    const energyCapacityAvailable = room.energyCapacityAvailable
    if (energyCapacityAvailable >= CONTROLLER_ENERGY[8] - 3000) {
        return [
            [WORK, 15],
            [CARRY, 5],
            [MOVE, 5],
        ]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[7]) {
        return [
            [WORK, 35],
            [CARRY, 5],
            [MOVE, 10],
        ]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[6]) {
        return [
            [WORK, 18],
            [CARRY, 2],
            [MOVE, 5],
        ]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[4]) {
        return [
            [WORK, 10],
            [CARRY, 2],
            [MOVE, 3],
        ]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[3]) {
        return [
            [WORK, 5],
            [CARRY, 1],
            [MOVE, 3],
        ]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[2]) {
        return [
            [WORK, 3],
            [CARRY, 1],
            [MOVE, 2],
        ]
    } else {
        return [
            [WORK, 2],
            [CARRY, 1],
            [MOVE, 1],
        ]
    }
}
